package com.shuigu.ai.controller;

import java.io.File;
import java.util.*;
import java.util.concurrent.TimeUnit;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.shuigu.ai.constant.AppConstant;
import com.shuigu.ai.factory.AssistantConfigFactory;
import com.shuigu.ai.model.request.*;
import com.shuigu.ai.model.enums.CodeGenTypeEnum;
import com.shuigu.ai.model.vo.AppVO;
import com.shuigu.ai.model.vo.MessageVO;
import com.shuigu.ai.model.vo.UserVO;
import com.shuigu.ai.ratelimiter.annotation.RateLimit;
import com.shuigu.ai.ratelimiter.enums.RateLimitType;
import com.shuigu.ai.service.ProjectDownloadService;
import com.shuigu.common.core.domain.R;
import com.shuigu.common.core.domain.entity.SysUser;
import com.shuigu.common.core.domain.model.LoginUser;
import com.shuigu.common.core.redis.RedisCache;
import com.shuigu.common.utils.SecurityUtils;
import com.shuigu.common.utils.uuid.UUID;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiChatRequestParameters;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import okhttp3.internal.http2.ErrorCode;
import org.apache.poi.ss.formula.functions.T;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.shuigu.common.annotation.Log;
import com.shuigu.common.core.controller.BaseController;
import com.shuigu.common.core.domain.AjaxResult;
import com.shuigu.common.enums.BusinessType;
import com.shuigu.ai.domain.App;
import com.shuigu.ai.service.IAppService;
import com.shuigu.common.utils.poi.ExcelUtil;
import com.shuigu.common.core.page.TableDataInfo;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 应用Controller
 * 
 * @author shuigu
 * @date 2025-09-14
 */
@RestController
@RequestMapping("/ai/app")
public class AppController extends BaseController
{
    @Autowired
    private IAppService appService;

    @Resource
    private AssistantConfigFactory assistantConfigFactory;

    @Resource
    private ProjectDownloadService projectDownloadService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 查询应用列表
     */
    @PreAuthorize("@ss.hasPermi('ai:app:list')")
    @GetMapping("/list")
    public TableDataInfo list(App app)
    {
        startPage();
        List<App> list = appService.selectAppList(app);
        return getDataTable(list);
    }

    /**
     * 导出应用列表
     */
    @PreAuthorize("@ss.hasPermi('ai:app:export')")
    @Log(title = "应用", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, App app)
    {
        List<App> list = appService.selectAppList(app);
        ExcelUtil<App> util = new ExcelUtil<App>(App.class);
        util.exportExcel(response, list, "应用数据");
    }

    /**
     * 获取应用详细信息
     */
    @PreAuthorize("@ss.hasPermi('ai:app:query')")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id)
    {
        return success(appService.selectAppById(id));
    }

    /**
     * 新增应用
     */
    @PreAuthorize("@ss.hasPermi('ai:app:add')")
    @Log(title = "应用", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody App app)
    {
        return toAjax(appService.insertApp(app));
    }

    /**
     * 修改应用
     */
    @PreAuthorize("@ss.hasPermi('ai:app:edit')")
    @Log(title = "应用", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody App app)
    {
        return toAjax(appService.updateApp(app));
    }

    /**
     * 删除应用
     */
    @PreAuthorize("@ss.hasPermi('ai:app:remove')")
    @Log(title = "应用", businessType = BusinessType.DELETE)
	@DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable Long[] ids)
    {
        return toAjax(appService.deleteAppByIds(ids));
    }


    /**
     * 用户创建应用
     *
     * @param appAddRequest 创建应用请求
     * @param request       请求
     * @return 应用 id
     */
    @Log(title = "应用", businessType = BusinessType.INSERT)
    @PostMapping("/add")
    public AjaxResult addApp(@RequestBody AppAddRequest appAddRequest, HttpServletRequest request) {
        Assert.notNull(appAddRequest, "参数不能为空");
        Long appId = appService.createApp(appAddRequest);
        stringRedisTemplate.delete("good:list");
        return AjaxResult.success(appId);
    }


    /**
     * 更新应用（用户只能更新自己的应用名称）
     *
     * @param appUpdateRequest 更新请求
     * @param request          请求
     * @return 更新结果
     */
    @Log(title = "应用", businessType = BusinessType.UPDATE)
    @PostMapping("/update")
    public AjaxResult updateApp(@RequestBody AppUpdateRequest appUpdateRequest, HttpServletRequest request) {
        if (appUpdateRequest == null || appUpdateRequest.getId() == null) {
            throw new RuntimeException( "参数不能为空");
        }
        Long userId = SecurityUtils.getUserId();
        long id = appUpdateRequest.getId();
        // 判断是否存在
        App oldApp = appService.getById(id);
        Assert.notNull(oldApp, "应用不存在");
        // 仅本人可更新
        if (!oldApp.getUserId().equals(userId)) {
            throw new RuntimeException("仅本人可以更新");
        }
        App app = new App();
        app.setId(id);
        app.setAppName(appUpdateRequest.getAppName());
        // 设置编辑时间
        app.setUpdateTime(new Date());
        boolean result = appService.updateById(app);
        stringRedisTemplate.delete("good:list");
        Assert.notNull(result, "更新应用失败");
        return AjaxResult.success(true);
    }

    /**
     * 删除应用（用户只能删除自己的应用）
     *
     * @param appDeleteRequest 删除请求
     * @param request       请求
     * @return 删除结果
     */
    @PostMapping("/delete")
    public AjaxResult deleteApp(@RequestBody AppDeleteRequest appDeleteRequest, HttpServletRequest request) {
        if (appDeleteRequest == null || appDeleteRequest.getId() <= 0) {
            throw new RuntimeException( "参数不能为空");
        }
        Long userId = SecurityUtils.getUserId();
        long id = appDeleteRequest.getId();
        // 判断是否存在
        App oldApp = appService.getById(id);
        Assert.notNull(oldApp, "应用不存在");
        // 仅本人或管理员可删除
        if (!oldApp.getUserId().equals(userId)) {
            throw new RuntimeException( "参数不能为空");
        }
        boolean result = appService.removeById(id);
        stringRedisTemplate.delete("good:list");
        return AjaxResult.success(result);
    }

    /**
     * 根据 id 获取应用详情
     *
     * @param id      应用 id
     * @return 应用详情
     */
    @GetMapping("/get/vo")
    public AjaxResult getAppVOById(long id) {
        Assert.isFalse(id < 0, "参数错误");
        // 查询数据库
        App app = appService.getById(id);
        Assert.notNull(app, "应用不存在");
        // 获取封装类（包含用户信息）
        return AjaxResult.success(appService.getAppVO(app));
    }

    /**
     * 分页获取当前用户创建的应用列表
     *
     * @param appQueryRequest 查询请求
     * @param request         请求
     * @return 应用列表
     */
    @GetMapping("/my/list/page/vo")
    public TableDataInfo listMyAppVOByPage(AppQueryRequest appQueryRequest, HttpServletRequest request) {
        Assert.notNull(appQueryRequest,"参数不能为空");
        Long userId = SecurityUtils.getUserId();
        // 只查询当前用户的应用
        appQueryRequest.setUserId(userId);
        App app = BeanUtil.copyProperties(appQueryRequest, App.class);
        startPage();
        List<AppVO> list = appService.selectMyAppList(app);
        list.forEach(appVO -> appVO.setUser(BeanUtil.copyProperties(SecurityUtils.getLoginUser().getUser(), UserVO.class)));
        return getDataTable(list);
    }


    /**
     * 分页获取精选应用列表
     *
     * @param appQueryRequest 查询请求
     * @return 精选应用列表
     */
    @GetMapping("/good/list/page/vo")
    public TableDataInfo listGoodAppVOByPage(AppQueryRequest appQueryRequest) {
        Assert.notNull(appQueryRequest,"参数不能为空");
        // 只查询精选的应用
        appQueryRequest.setPriority(AppConstant.GOOD_APP_PRIORITY);
        App app = BeanUtil.copyProperties(appQueryRequest, App.class);

        String appStr = stringRedisTemplate.opsForValue().get("good:list");
        if (StrUtil.isNotBlank(appStr)){
            List<AppVO> list = JSONUtil.toList(appStr, AppVO.class);
            list.forEach(appVO -> appVO.setUser(BeanUtil.copyProperties(SecurityUtils.getLoginUser().getUser(), UserVO.class)));
            return getDataTable(list);
        }
        // 判断命中的是否是空值,防止缓存穿透
        if(appStr != null){
            return getDataTable(Collections.emptyList());
        }
        startPage();
        List<AppVO> list = appService.selectGoodAppList(app);

        if(list.isEmpty()){
            stringRedisTemplate.opsForValue().set("good:list", "",60, TimeUnit.SECONDS);
            return getDataTable(list);
        }
        stringRedisTemplate.opsForValue().set("good:list", JSONUtil.toJsonStr(list),60, TimeUnit.SECONDS);
        return getDataTable(list);
    }

    /**
     * 应用聊天生成代码（流式 SSE）
     *
     * @param appId   应用 ID
     * @param message 用户消息
     * @param request 请求对象
     * @return 生成结果流
     */
    @RateLimit(limitType = RateLimitType.USER, rate = 5, rateInterval = 60, message = "AI 对话请求过于频繁，请稍后再试")
    @GetMapping(value = "/chat/gen/code", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> chatToGenCode(@RequestParam Long appId,
                                                       @RequestParam String message,
                                                       @RequestParam(defaultValue = "false") boolean agent,
                                                       HttpServletRequest request) {
        // 参数校验
        Assert.isFalse(appId == null || appId <= 0,"应用 ID 无效");
        Assert.notBlank(message,"用户消息不能为空");
        // 获取当前登录用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        SysUser user = loginUser.getUser();
        // 调用服务生成代码（流式）
        Flux<String> contentFlux = appService.chatToGenCode(appId, message, user, agent);
        // 转换为 ServerSentEvent 格式
        return contentFlux
                .map(chunk -> {
                    // 将内容包装成JSON对象
                    Map<String, String> wrapper = Map.of("d", chunk);
                    String jsonData = JSONUtil.toJsonStr(wrapper);
                    return ServerSentEvent.<String>builder()
                            .data(jsonData)
                            .build();
                })
                .concatWith(Mono.just(
                        // 发送结束事件,之后前端需要从 data 块中取出数据，通过判断 event = done 判断事件结束，主要用于判断流式输出是正常中断还是异常中断
                        ServerSentEvent.<String>builder()
                                .event("done")
                                .data("")
                                .build()
                ));
    }

    /**
     * 应用部署
     *
     * @param appDeployRequest 部署请求
     * @param request          请求
     * @return 部署 URL
     */
    @PostMapping("/deploy")
    public AjaxResult deployApp(@RequestBody AppDeployRequest appDeployRequest, HttpServletRequest request) {
        Assert.notNull(appDeployRequest,"参数不能为空");
        Long appId = appDeployRequest.getAppId();
        Assert.isFalse(appId == null || appId <= 0, "应用 ID 不能为空");
        // 获取当前登录用户
        SysUser user = SecurityUtils.getLoginUser().getUser();
        // 调用服务部署应用
        String deployUrl = appService.deployApp(appId, user);
        return AjaxResult.success("部署成功",deployUrl);
    }

    /**
     * 根据应用和用户获取会话记忆
     * @param appId
     * @return
     */
    @GetMapping("/chat/messages")
    public AjaxResult getChatMessages(Long appId) {
        Assert.isFalse(appId == null || appId <= 0, "应用 ID 不能为空");

        ChatMemoryProvider chatMemoryProvider = assistantConfigFactory.getChatMemoryProvider(appId);
        MessageWindowChatMemory chatMemory = (MessageWindowChatMemory) chatMemoryProvider.get(appId);
        List<ChatMessage> chatMessages = chatMemory.messages();

        List<MessageVO> messageVOS = MessageVO.serializationMessage(chatMessages);

        return AjaxResult.success(messageVOS);
    }

    /**
     * 下载应用代码
     *
     * @param appId    应用ID
     * @param request  请求
     * @param response 响应
     */
    @GetMapping("/download/{appId}")
    public void downloadAppCode(@PathVariable Long appId,
                                HttpServletRequest request,
                                HttpServletResponse response) {
        // 1. 基础校验
        Assert.isFalse(appId == null || appId <= 0, "应用ID无效");
        // 2. 查询应用信息
        App app = appService.getById(appId);
        Assert.notNull(app,"应用信息不能为空");
        // 3. 权限校验：只有应用创建者可以下载代码
        SysUser loginUser = SecurityUtils.getLoginUser().getUser();
        if (!app.getUserId().equals(loginUser.getUserId())) {
            throw new RuntimeException("无权限下载该应用代码");
        }
        // 4. 构建应用代码目录路径（生成目录，非部署目录）
        String codeGenType = app.getCodeGenType();
        String sourceDirName = codeGenType + "_" + appId;
        String sourceDirPath = AppConstant.CODE_OUTPUT_ROOT_DIR + File.separator + sourceDirName;
        // 5. 检查代码目录是否存在
        File sourceDir = new File(sourceDirPath);
        Assert.isTrue(sourceDir.exists() && sourceDir.isDirectory(), "应用代码不存在，请先生成代码");
        // 6. 生成下载文件名（不建议添加中文内容）
        String downloadFileName = String.valueOf(appId);
        // 7. 调用通用下载服务
        projectDownloadService.downloadProjectAsZip(sourceDirPath, downloadFileName, response);
    }

    /**
     * 轮询预览
     * @param appId
     * @param request
     * @return
     */
    @GetMapping("/build/status/{appId}")
    public AjaxResult getBuildStatus(@PathVariable Long appId, HttpServletRequest request) {
        // 参数校验和权限检查
        Assert.isFalse(appId == null || appId <= 0, "应用ID无效");
        SysUser loginUser = SecurityUtils.getLoginUser().getUser();
        App app = appService.getById(appId);
        Assert.notNull(app,"应用不存在");
        if (!app.getUserId().equals(loginUser.getUserId())) {
            throw new RuntimeException("无权限查询构建状态");
        }
        // 检查构建状态
        String projectPath = AppConstant.CODE_OUTPUT_ROOT_DIR + File.separator + "vue_project_" + appId;
        File projectDir = new File(projectPath);
        Map<String, Object> buildStatus = getBuildStatus(appId, projectDir);
        return AjaxResult.success(buildStatus);
    }

    @NotNull
    private static Map<String, Object> getBuildStatus(Long appId, File projectDir) {
        File distDir = new File(projectDir, "dist");
        Map<String, Object> buildStatus = new HashMap<>(16);
        buildStatus.put("appId", appId);
        buildStatus.put("projectExists", projectDir.exists());
        buildStatus.put("distExists", distDir.exists());
        // 同步构建模式下总是false
        buildStatus.put("isBuilding", false);
        if (distDir.exists()) {
            buildStatus.put("status", "completed");
            buildStatus.put("message", "构建已完成");
            buildStatus.put("buildTime", distDir.lastModified());
        } else if (projectDir.exists()) {
            buildStatus.put("status", "pending");
            buildStatus.put("message", "项目已生成，等待构建");
        } else {
            buildStatus.put("status", "not_found");
            buildStatus.put("message", "项目不存在");
        }
        return buildStatus;
    }

}
